using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using CommonServiceManager;
using Microsoft.Practices.Unity;
using UnityCommonServiceManagerAdapter.Properties;

namespace UnityCommonServiceManagerAdapter
{
	/// <summary>
	/// <see cref="IServiceLocator"/> using <see cref="IUnityContainer"/>
	/// </summary>
	public class UnityServiceLocator : IServiceLocator
	{
		private readonly IUnityContainer unityContainer;

		/// <summary>
		/// Create an instance of the locator
		/// </summary>
		/// <param name="unityContainer">The current <see cref="IUnityContainer"/></param>
		public UnityServiceLocator(IUnityContainer unityContainer)
		{
			this.unityContainer = unityContainer;
		}

		/// <summary>
		/// <see cref="IServiceProvider.GetService"/>
		/// </summary>
		public object GetService(Type serviceType)
		{
			return GetInstance(serviceType);
		}

		/// <summary>
		/// This implementation allows for deffered evaluation by returning an evaluator for
		/// <see cref="IEnumerable{T}"/> of <typeparam name="TService" /> and not the actual enumeration.
		/// <see cref="IServiceLocator.GetAllInstances{TService}"/>
		/// </summary>
		public IEnumerable<TService> GetAllInstances<TService>()
		{
			return new AllInstances<TService>(
				() =>
				{
					try
					{
						// Short cirquit the IEnumerable (Linq Expression) here in order to catch
						// possible resolve fails and return our own exception.
						return unityContainer.ResolveAll<TService>()
																 .ToArray();
					}
					catch (ResolutionFailedException rfe)
					{
						throw ActivationException(rfe, typeof(TService));
					}
				}
			);
		}

		/// <summary>
		/// <see cref="IServiceLocator.GetInstance"/>
		/// </summary>
		public object GetInstance(Type serviceType, string implementationKey = null)
		{
			try
			{
				return unityContainer.Resolve(serviceType, implementationKey);
			}
			catch (ResolutionFailedException rfe)
			{
				throw ActivationException(rfe, serviceType, implementationKey);
			}
		}

		/// <summary>
		/// <see cref="IServiceLocator.GetInstance{TService}"/>
		/// </summary>
		public TService GetInstance<TService>(string implementationKey = null)
		{
			try
			{
				return unityContainer.Resolve<TService>(implementationKey);
			}
			catch (ResolutionFailedException rfe)
			{
				throw ActivationException(rfe, typeof(TService), implementationKey);
			}
		}

		/// <summary>
		/// This implementation allows for deffered evaluation by returning an evaluator for
		/// <see cref="IEnumerable{T}"/> of <see cref="object"/> and not the actual enumeration.
		/// <seealso cref="IServiceLocator.GetAllInstances"/>
		/// </summary>
		public IEnumerable<object> GetAllInstances(Type serviceType)
		{
			return new AllInstances<object>(
				() =>
				{
					try
					{
						// Short cirquit the IEnumerable (Linq Expression) here in order to catch
						// possible resolve fails and return our own exception.
						return unityContainer.ResolveAll(serviceType)
																 .ToArray();
					}
					catch (ResolutionFailedException rfe)
					{
						throw ActivationException(rfe, serviceType);
					}
				}
			);
		}

		private static ActivationException ActivationException(Exception innerException, Type type, string key = null)
		{
			var message = key == null ?
										string.Format(Strings.ActivationExceptionMessage, type.FullName) :
										string.Format(Strings.ActivationExceptionMessageWithKey, type.FullName, key);
			return new ActivationException(message, innerException);
		}

		/// <summary>
		/// The use of this implementation of <see cref="IEnumerable{T}"/> makes us able to use deferred execution (of sorts) on the results of a call to
		/// <see cref="UnityServiceLocator.GetAllInstances"/> or <see cref="UnityServiceLocator.GetAllInstances{TService}"/>.
		/// </summary>
		/// <remarks>The deferred execution will return all intaces once called but we can wait to the right moment to call which still gives us some deferred execution capability.</remarks>
		/// <typeparam name="T">The type of instance being activated.</typeparam>
		private class AllInstances<T> : IEnumerable<T>
		{
			private readonly Func<IEnumerable<T>> functionToGetAll;

			public AllInstances(Func<IEnumerable<T>> functionToGetAll)
			{
				this.functionToGetAll = functionToGetAll;
			}

			public IEnumerator<T> GetEnumerator()
			{
				var allInstancesOfT = functionToGetAll();
				return allInstancesOfT.GetEnumerator();
			}

			IEnumerator IEnumerable.GetEnumerator()
			{
				return GetEnumerator();
			}
		}
	}
}